import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FileSplitMerge {
    // 1MB
    private static final int FILE_SIZE = 1 * 1024 * 1024;
    // 文件结束标识
    private static final int EOF = -1;
    //文件分块保存的大小 1024 * 1024 即 1MB
    private static final int SAVE_SIZE = 1 * 1024 * 1024;

    //文件切片(磁盘到磁盘)
    //功能: 文件切片 + 制作种子文件
    static boolean fileSplit(String filename) {
        //只能以".class"文件的位置为根的相对路径中寻找文件
        String class_path = FileSplitMerge.class.getResource("/").getPath();


        try {
            //STEP0 创建字节输入流
            BufferedInputStream bis_MD5 = new BufferedInputStream(new FileInputStream(class_path + filename));//这个字节流用于计算整个文件的MD5值
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(class_path + filename));

            //STEP1 计算整个文件的MD5值
            MessageDigest MD5_whole_file = MessageDigest.getInstance("MD5");
            byte[] buffer_MD5 = new byte[8192];
            int lengthbuffer_MD5;
            while ((lengthbuffer_MD5 = bis_MD5.read(buffer_MD5)) != -1) {
                MD5_whole_file.update(buffer_MD5, 0, lengthbuffer_MD5);
            }
            byte[] MD5_whole_file_byte = MD5_whole_file.digest();//.digest()函数只允许调用一次!!之后再调用就是初始化值
            String MD5_whole_file_str = Md5CaculateUtil.MD5ArrFromHexToString(MD5_whole_file_byte);

//============文件块内存储的MD5值=======
            System.out.print("MD5_whole_file_byte: ");
            for(int i=0;i<MD5_whole_file_byte.length;i++){
                System.out.print(MD5_whole_file_byte[i] + " ");
            }System.out.println();
            System.out.println("MD5_whole_file_str: " + MD5_whole_file_str);
//=======================================

            //STEP2 创建文件片存储的文件夹(文件夹名称为MD5字符串)
            //String filename_no_suffix = filename.substring(0, filename.lastIndexOf('.'));//获取无后缀的文件名
            File chunk_floder = new File(class_path + MD5_whole_file_str);//文件夹的路径
            chunk_floder.isDirectory();
            if (!chunk_floder.isDirectory()) {//判断这个名字的文件夹是否存在,如果存在就无需创建了
                if (!chunk_floder.mkdirs()) {
                    System.out.println("创建文件片存储文件夹失败");
                    return false;
                }
            }
            //chunk_floder.getPath()

            //----------------
            //"自己的文件持有表"
            FileHoldingListSelf fhls = new FileHoldingListSelf();
            fhls.setFile_name(filename);
            fhls.setFile_whole_md5(MD5_whole_file_str);
            fhls.setIs_total_rcv(true);
            //fhls.file_chunk_list = new
            List<FCL_S_item> file_chunk_list = new ArrayList<>();
            //----------------

            //STEP3 开始拆分文件
            //原理就是"读一段,写一段"
            int available = bis.available();
            System.out.println(available);//输出文件字节数
            //输出文件的大小(单位为MB)
            System.out.println(String.format("%.2fMB", available * 1.0 / FILE_SIZE));

            // 拆分成每个为 SAVE_SIZE个bit 大小的文件
            byte[] byte_arr = new byte[SAVE_SIZE];

            int length = EOF;
            // 子文件下标
            int filenameExt = 0;
            while ((length = bis.read(byte_arr)) > EOF) {//读入一管子缓冲区的字节流, 读入一个字节数组, 即一个文件片
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(chunk_floder.getPath() + File.separator + (filenameExt)));
                bos.write(MD5_whole_file_byte);//写入 128位MD5值(16个byte)(写入的是整个文件的MD5值,这个MD5值作为文件名来用,即区分这个文件块是属于哪个文件)
                bos.write(NetChunkUtil.intToByteArray(filenameExt));//写入 32位序号值(4个byte)
                bos.write(byte_arr, 0, length);
                bos.flush();

                MessageDigest MD5_chunk = MessageDigest.getInstance("MD5");
                MD5_chunk.update(byte_arr, 0, length);
                String MD5_chunk_str = Md5CaculateUtil.MD5ArrFromHexToString(MD5_chunk.digest());

                file_chunk_list.add(new FCL_S_item(filenameExt, MD5_chunk_str, true, chunk_floder.getPath() + File.separator + filenameExt));
                //try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(class_path + "sub-big-" + (filenameExt++) + ".data"));) {
                //    bos.write(byte_arr, 0, length);
                //}
                filenameExt++;
            }
            bis.close();

            fhls.setFile_chunk_list(file_chunk_list.stream().toArray(FCL_S_item[]::new));

            File FileHoldingListSelf_file = new File(class_path + filename + ".json");
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(FileHoldingListSelf_file), "Utf-8"));
            bw.write(fhls.toJSON().toString());
            bw.flush();


        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return true;
    }

    //文件片合并(磁盘到磁盘)
    static boolean fileMerge(String file_MD5_str) {
        String path = FileSplitMerge.class.getResource("/").getPath() + File.separator + file_MD5_str + File.separator;
        //STEP1 文件名的正则表达式(文件名全是数字)
        String filename_regular = "\\d+";
        //STEP2 列出路径中所有文件 file是一个路径
        File file_path = new File(path);
        if (!file_path.isDirectory()) {
            System.out.println("文件片文件夹不合法");
            return false;
        }
        String name = file_path.getName();
        System.out.println(name);
        String[] list = file_path.list();//列出路径中所有文件  File.list()此方法的返回类型为File []，即，如果给定路径为目录，则返回文件路径中表示的文件数组，否则返回null。
        //STEP2 利用"Stream"的方法进行正则表达式匹配,筛选出符合正则表达式的文件名,存入List subFileNames
        Stream<String> stream = Arrays.stream(list);

        class item {//用于排序的临时内部来
            String filename;
            int val;

            public int getVal() {
                return val;
            }

            public String getFilename() {
                return filename;
            }

            item(String s) {
                filename = s;
                val = Integer.parseInt(filename);
            }
        }
        List<item> item_list = stream.filter(str -> {
            return str.matches(filename_regular);
        }).map(str -> new item(str)).collect(Collectors.toList());
        List<String> filename_array = item_list.stream().sorted(Comparator.comparing(item::getVal)).map(item::getFilename).collect(Collectors.toList());

        filename_array.forEach(i -> {
            System.out.println(i);
        });

        if (filename_array == null || filename_array.size() < 1) {
            return false;
        }
        // 组装文件
        BufferedOutputStream bos = null;
        try {
            bos = new BufferedOutputStream(new FileOutputStream(path + "composite-big.data"));
            for (String subFilename : filename_array) {
                try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path + subFilename));) {
                    int available = bis.available();
                    System.out.println(available);
                    // 每次读取1Mb大小的文件
                    byte[] bytes = new byte[FILE_SIZE];
                    int length = EOF;
                    boolean is_start = true;//是否为第一次读取(第一次读取会读取到对象的头部,要去除头部的md5值和序号)
                    while ((length = bis.read(bytes)) > EOF) {
                        if (is_start) {
//============把文件块内存储的MD5值拿出来看看=======
                            //byte[] md5_byte = new byte[16];
                            //System.arraycopy(bytes,0,md5_byte,0,16);
                            //System.out.println(Md5CaculateUtil.MD5ArrFromHexToString(md5_byte));//果然是分块出错了
//======================================
                            bos.write(bytes, 20, length - 20);//"20"是去除的头部的值
                            is_start = false;
                        } else
                            bos.write(bytes, 0, length);
                    }
                    bos.flush();//读完最后一块,报了EOF后,记得flush,因为缓冲区可能还没全部发送出去
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            bos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


        return false;
    }

    public static void main(String[] args) throws IOException {
        fileSplit("jdk api 1.8_google.CHM");
        fileMerge("07FD69AD27BF2F633A20A2A4F3FC4807");
    }
}
